// Copyright (C) 2008 Taylor L. Riche <riche@cs.utexas.edu>
//  
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software 
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//

/**
 * 
 */
package appcomp.entities;

import java.net.URI;
import java.util.LinkedList;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.HashMap;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.lang.Exception;

import core.CodeGenerator;
import utility.Pair;

/**
 * @author riche
 *
 */
public class Composite extends Operator {

	private LinkedList<Operator> opList;
	private LinkedList<Connector> conList;
	
	public Composite() {
//		Random r = new Random();
//		String token = Long.toString(Math.abs(r.nextLong()), 36);
//		this.setName(token);
		opList = new LinkedList<Operator>();
		conList = new LinkedList<Connector>();
		includeMap = new HashMap<String, Boolean>();
		delayMap = new HashMap<String, Double>();
		this.slack = Double.MAX_VALUE;
		this.maxQueueLength = Integer.MIN_VALUE;
	}

	public void setCompositeName() {
		if(!opList.isEmpty()) {
			String compname = "";
			for(Operator o : this.opList) {
				compname += o.getName().substring(0, 5);
			}
			this.setName(compname);
		}
	}
	
	/**
	 * @return the conList
	 */
	public LinkedList<Connector> getConList() {
		return conList;
	}

	/**
	 * @return the opList
	 */
	public LinkedList<Operator> getOpList() {
		return opList;
	}
	
	/**
	 * @param o The Operator to add to the list.
	 */
	public void addToOpList(Operator o) {
		opList.add(o);
	}

	/**
	 * @param c The Connector to add to the list.
	 */
	public void addToConList(Connector c) {
		conList.add(c);
	}

	/* (non-Javadoc)
	 * @see appcomp.entities.Operator#consumeConnectors(java.util.List, java.util.List)
	 */
	@Override
	public void consumeConnectors(List<Connector> oldList,
			List<Connector> newList) {
	    int portIndex = 2;
		ArrayList<Connector> deadList = new ArrayList<Connector>();
		for(Operator o : this.opList) {
			for(Connector c : oldList) {
				if(c.getIn() == o) {
					if(this.opList.contains(c.getOut())) {
						this.conList.add(c);
						Port op = o.getPortByName(c.getInName().getRight());
						assert(op != null);
						CompositePort p = new CompositePort(o.getName() + "_" + op.getName(), CompositePort.Visibility.INTERNAL, c, op, o);
						p.setIndex(portIndex);
						portIndex++;
						this.portList.add(p);
					}
					else {
						Connector con = Connector.create(new Pair<String, String>(this.getName(), o.getName() + "_" + c.getInName().getRight()), 
								c.getOutName());
						con.setIn(this);
						con.setOut(c.getOut());
						newList.add(con);
						Port op = o.getPortByName(c.getInName().getRight());
						assert(op != null);
						CompositePort p = new CompositePort(o.getName() + "_" + op.getName(), CompositePort.Visibility.EXTERNAL, con, op, o);
                        p.setIndex(portIndex);
                        portIndex++;
						this.portList.add(p);						
					}
					deadList.add(c);
				}
				else if(c.getOut() == o) {
					if(this.opList.contains(c.getIn())) {
						this.conList.add(c);
						Port op = o.getPortByName(c.getOutName().getRight());
						assert(op != null);
						CompositePort p = new CompositePort(o.getName() + "_" + op.getName(), CompositePort.Visibility.INTERNAL, c, op, o);
                        p.setIndex(portIndex);
                        portIndex++;
						this.portList.add(p);
					}
					else {
						Connector con = Connector.create(c.getInName(), 
								new Pair<String, String>(this.getName(), o.getName() + "_" + c.getOutName().getRight()));
						con.setIn(c.getIn());
						con.setOut(this);
						newList.add(con);
						Port op = o.getPortByName(c.getOutName().getRight());
						assert(op != null);
						CompositePort p = new CompositePort(o.getName() + "_" + op.getName(), CompositePort.Visibility.EXTERNAL, con, op, o);
                        p.setIndex(portIndex);
                        portIndex++;
						this.portList.add(p);						
					}
					deadList.add(c);
				}
			}
//			for(Connector c : deadList) {
//				oldList.remove(c);
//			}
		}
	}
	
	public void printPorts() {
		System.out.println(this.getName());
		for(Port p : this.portList) {
			System.out.println("\t" + p.printFullName());
		}
	}

	/* (non-Javadoc)
	 * @see appcomp.entities.Operator#generateCode(core.CodeGenerator)
	 */
	@Override
	public void generateCode(CodeGenerator cg) {
		//for(Operator o : this.opList) {
		//	o.generateCode(cg);
		//}
        String filename = "lc_" + this.getName() + "_Operator.h";
	    for(Operator o : this.opList) {
	        String tempFilename = "lc_" + o.getName() + "_Operator.h";
	        FileOutputStream tempOut = null;
	        PrintStream tempFile = null;
	        try {
	            tempOut = new FileOutputStream(tempFilename);
	            tempFile = new PrintStream(tempOut);
	        }
	        catch (FileNotFoundException e) {
	            System.err.println("Specified file " + tempFilename + " does not exist.");
	            System.exit(1);
	        }
	        tempFile.println("#include \"" + filename + "\"");
	        tempFile.close();
	    }
        String accFilename = "lc_acc.cc";
        FileOutputStream accOut = null;
        PrintStream accFile = null;
        try {
            accOut = new FileOutputStream(accFilename);
            accFile = new PrintStream(accOut);
        }
        catch (FileNotFoundException e) {
            System.err.println("Specified file " + filename + " does not exist.");
            System.exit(1);
        }
        accFile.println("#include \"" + filename + "\"" );
        accFile.close();
		FileOutputStream out = null;
		PrintStream file = null;
		try {
		    out = new FileOutputStream(filename);
		    file = new PrintStream(out);
		}
		catch (FileNotFoundException e) {
		    System.err.println("Specified file " + filename + " does not exist.");
		    System.exit(1);
		}

		// Print guards
		file.println("#ifndef LC_" + this.getName().toUpperCase() + "_H_");
		file.println("#define LC_" + this.getName().toUpperCase() + "_H_");
		file.println();

		// Print standard header info
		file.println("#include <assert.h>");
		file.println("#include <lagniappe/Operator.h>");
		file.println("#include <lagniappe/OpShell.h>");
		file.println("#include <lagniappe/RCon.h>");
		file.println("#include <lagniappe/RData.h>");
		file.println("#include \"cmdline.h\"");

        // Print includes for incoming and outgoing data types
        for(Port p : this.portList) {
            p.setFile(file);
            p.generateInclude(this.includeMap);
        }
        for (Operator o : this.opList){
            try {
                if (o.getState().getFileURI() != null) {
                    file.println("#include \""
                            + o.getState().getFileURI().toString() + "\"");
                }
            } catch (Exception e) {
            }
        }
        if(includeFile != null) {
            file.println("#include \"" + includeFile.toString() + "\"");
        }

        file.println();
        file.println("class " + this.getName() + ";");
        
        // Print classes for old operators
        
        file.println();
        
        for(Operator o : this.opList) {
            file.println("class " + o.getName() + " : public lagniappe::OpShell {");
            file.println("protected:");
            file.println("\t" + this.getName() + "* realParent;");
            if(o.isStateful) {
                // yeah, I don't like this either
                // passing the file output stream is a little clunky
                o.state.setFile(file);
                o.state.generateCode(cg);
                if(o.state.getFlowSig() != null) {
                    file.println("\tlagniappe::FlowID getFlowID(" + o.state.getFlowSig().getRefType() + "* data) {");
                    // use the getFlowID method provided by the user
                    file.println("\t\tlagniappe::FlowID retval = data->" + o.state.getFlowSig().getGetFlowID() + "();");
                    //file.println("\t\tdelete data;");
                    file.println("\t\treturn retval;");
                    file.println("\t}");
                }
                try {
                    // TODO add flow signature information in these method arguments
                    // TODO figure out exactly what it means to auto-generate composite flow maintenance methods
                    file.println("\tvoid install(void * data) {" + o.getState().getInstall() + "(data);}");
                    file.println("\tvoid * get() {return " + o.getState().getGet() + "();}");
                    file.println("\tvoid purge(void * data) {" + o.getState().getPurge() + "(data);}");
                    file.println("\tvoid init() {" + o.getState().getInit() + "();}");
                }
                catch (Exception e) {}
              }
            
            

            file.println("public:");
            file.println("\t" + o.getName() + "(lagniappe::System * sys, bool prof, " + this.getName() + " * pp) : lagniappe::OpShell(sys, prof), realParent(pp) {");
            if(o.isStateful) {
                file.println("\t\tthis->init();");
            }
            file.println("}");
            for(Port p : o.getPortList()) {
                p.setFile(file);
                p.setInComp(true);
                p.setParentOp(o);
                p.generateCode(cg);
            }
            file.println();
            file.println("};");
            file.println();
        }

        // Start class definition
        file.println();
        file.println("class " + this.getName() + " : public lagniappe::Operator {");

        // private data declaration
        file.println("private:");
        if(this.isStateful) {
            // yeah, I don't like this either
            // passing the file output stream is a little clunky
            state.setComp(true);
            state.setFile(file);
            state.generateCode(cg);
//            if(this.state.getFlowSig() != null) {
//                file.println("\tvirtual void reassignFlow(lagniappe::ProcElement * proc) {");
//                file.println("\t\tfor(std::map<lagniappe::FlowID, std::pair<lagniappe::ProcElement *, unsigned int> *>::iterator i = flowMap.begin(); i != flowMap.end(); ++i) {");
//                file.println("\t\t\tif(i->second->first == proc) {i->second->first = procList[static_cast<unsigned long>(i->first % procList.size())]; i->second->second = procList.size(); break;}");
//                file.println("\t\t}");
//                file.println("\t}");
//            }
        }
        
        // declare an instance of each member operator
        for(Operator o : this.opList) {
            file.println("\t" + o.getName() + " local_" + o.getName() + ";");
        }
        
        // define handlers as private methods
        file.println();
        for(Port p : this.portList) {
            p.generateCode(cg);
        }
        file.println("gengetopt_args_info * args;");
		file.println("\tbool first;");
        //file.println("\tstd::vector<lagniappe::ProcElement *>::iterator procIter;");
        // start public methods
        file.println();
        file.println("public:");
        
        file.print("\t" + this.getName() + "(std::string s, lagniappe::Mutex * m, lagniappe::System * sys, void * arg_struct, bool prof = false) : lagniappe::Operator(s, m, sys, prof)");
        for(Operator o : this.opList) {
            file.print(", local_" + o.getName() + "(sys, prof, this) ");
        }
        file.println("{");
        
        file.println("\t\tif(arg_struct != NULL) args = (gengetopt_args_info *)arg_struct;");
        if(this.isStateful) {
            file.println("\t\tthis->init();");
            file.println("\t\tflowMap.resize(100);");
        }
        file.println("\t\tdestVec.resize(" + (this.portList.size()+2) + ");");
        file.println("\t\tfirst = true;");
        file.println("\t}");

        if(this.isStateful) {
            // override data maintenance methods
            try {
                // TODO add flow signature information in these method arguments
                // TODO figure out exactly what it means to auto-generate composite flow maintenance methods
                file.println("\t//void install(void * data) {" + this.getState().getInstall() + "(data);}");
                file.println("\t//void * get() {return " + this.getState().getGet() + "();}");
                file.println("\t//void purge(void * data) {" + this.getState().getPurge() + "(data);}");
                file.println("\t//void init() {" + this.getState().getInit() + "();}");
                file.println("\tvoid init() {}");
            }
            catch (Exception e) {}
            file.println("\tvirtual lagniappe::ProcElement * getProc(lagniappe::RCon * rcon) {");
            file.println("\t\tassert(!procList.empty());");
            file.println("\t\tassert(rcon->getRData() != NULL);");
			file.println("\t\tif(first) {iter = procList.begin(); first = false;}");
            file.println("\t\tbool roundRobin = true;");
            file.println("\t\tlagniappe::ProcElement * retVal = NULL;");
            if(this.state.getFlowSig() != null) {
                file.println("\t\tstd::pair<lagniappe::ProcElement *, unsigned int> * retPair = NULL;");
                for(Port p : this.portList) {
                    if(p.getDirection() == Port.Direction.IN && ((CompositePort)p).getPortVis() == CompositePort.Visibility.EXTERNAL) {
                        if(p.getType().getCoreType().equals(this.state.getFlowSig().getRefType())) {
                            // if Rcon->port = p
                            file.println("\t\tif(rcon->getPort() == " + p.getIndex() + ") {");
                            // call getFlowID(RCon->data)
                            // lookup flow id in table, if not there, make new entry, if is there, send request to processor in table
                            // we now have a flow map that keeps track of flow pinnings
                            file.println("\t\t\tlagniappe::FlowID fid = getFlowID(static_cast<" + p.getType().getCoreType() + " *>(rcon->getRData()));");
                            file.println("\t\t\tif((retPair = flowMap[fid%100]) == NULL) {");
							//file.println("\t\t\t\tretPair = flowMap[fid%100] = new std::pair<lagniappe::ProcElement *, unsigned int>(procList[(int)(procList.size() * (rand() / (RAND_MAX + 1.0)))], procList.size());");
							file.println("\t\t\t\tretPair = flowMap[fid%100] = new std::pair<lagniappe::ProcElement *, unsigned int>(*iter, procList.size());");
							file.println("\t\t\t\titer++; if(iter == procList.end()) {iter = procList.begin();}");
                            file.println("\t\t\t}");
                            file.println("\t\t\telse if(retPair->second < procList.size()) {");
							//file.println("\t\t\t\tretPair->first = procList[(int)(procList.size() * (rand() / (RAND_MAX + 1.0)))];");
                            //file.println("\t\t\t\tif(*procIter == NULL || procIter == procList.end()) {procIter = procList.begin();}");
							file.println("\t\t\t\tretPair->first = *iter;");
							file.println("\t\t\t\titer++; if(iter == procList.end()) {iter = procList.begin();}");
                            file.println("\t\t\t\tretPair->second = procList.size();");
                            file.println("\t\t\t}");
                            // following line redoes pinning every time a resource is added/removed, but has no state
                            //file.println("\t\t\tretVal = procList[static_cast<unsigned long>(fid % procList.size())];");
                            file.println("\t\t\tretVal = retPair->first;");
                            // set key to false
                            file.println("\t\t\troundRobin = false;");
                            file.println("\t\t}");
                        }
                    }
                }
            }
            file.println("\t\tif (roundRobin) {");
            file.println("\t\t\tint size = procList.size();");
            file.println("\t\t\tint rnum = (int)(size * (rand() / (RAND_MAX + 1.0)));");
            file.println("\t\t\tassert(rnum < size);");
            file.println("\t\t\tretVal = procList[rnum];");
            file.println("\t\t}");
            file.println("\t\treturn retVal;");
            file.println("\t}");
            file.println();
            if(this.state.getFlowSig() != null) {
                file.println("\tlagniappe::FlowID getFlowID(" + this.state.getFlowSig().getRefType() + "* data) {");
                // use the getFlowID method provided by the user
                file.println("\t\tlagniappe::FlowID retval = data->" + this.state.getFlowSig().getGetFlowID() + "();");
                //file.println("\t\tdelete data;");
                file.println("\t\treturn retval;");
                file.println("\t}");
            }
        }
        
        file.println();
        file.println("\tvirtual void processRequest(lagniappe::RCon * rcon) {");
        file.println();
        if(!this.doesGenerate()) {
            for(Port p : this.portList) {
                if(p.getDirection() == Port.Direction.IN) {
                    p.generateCodeCases();
                }
            }
        }
        file.println("\t\tif(rcon->getPort() == 0) {");
        file.println("\t\t\tLC_handleMonitor(rcon->getRData());");
        file.println("\t\t}");
        file.println("\t\tdelete rcon;");
        file.println("\t}");

        // register queues
        // TODO: Move a print registration into Port so that only externals can get printed here
        //       similar to how generateCodeCases works
        file.println("\tvirtual void registerPorts(lagniappe::CommChannel * chan) {");
        for(Port p : this.portList) {
            if(p.getDirection() == Port.Direction.IN) {
                file.println("\t\tchan->registerQueue(" + p.getIndex() + ");");
            }
        }
        file.println("\t\tchan->registerQueue(0);");
        file.println("\t\t");
        file.println("\t}");

        // Generate list of RCons
        file.println();
        if(!this.doesGenerate()) {
            file.println("\tvirtual std::list<lagniappe::RCon *> * generateRequestList() {");
            file.println("\t\tstd::list<lagniappe::RCon *> * retList = new std::list<lagniappe::RCon *>;");
            for(Port p : this.portList) {
                if(p.getDirection() == Port.Direction.IN) {
                    p.generateProfCode();
                }
            }
            file.println("\t\treturn retList;");
            file.println("\t}");
        }
        
        if(this.maxQueueLength > Integer.MIN_VALUE) {
            file.println("\tvirtual unsigned int getQueueThreshold() {return " + this.maxQueueLength + ";}");
        }
        
        Boolean hasPred = false;
        for(Port p : this.portList) {
            if(p.getPredicate() != null) {
                hasPred = true;
                file.println("\tbool " + p.getPredicate() + "();");
            }
        }
        if(hasPred) {
            file.println("\tvirtual bool pred(std::string port) {");
            for(Port p : this.portList) {
                if(p.getPredicate() != null) {
                    file.println("\t\tif(port == \"" + p.getName() + "\") {return " + p.getPredicate() + "();}");
                }
            }
            file.println("\t\treturn true;");
            file.println("\t}");
        }
        
        for(Operator o : this.opList) {
            file.println("\tfriend class " + o.getName() + ";");
        }
		
		// Close out #ifndef
		file.println("};");
		file.println("#endif // LC_"+ this.getName().toUpperCase() + "_H_");
		
	}
}
